package miggy.cpu;

import miggy.SystemModel;
import miggy.api.config.ConfigurationException;
import miggy.api.config.MachineDefinition;
import miggy.api.cpu.*;

import java.io.File;

/*
//  Miggy - Java Amiga MachineCore
//  Copyright (c) 2008, Tony Headford
//  All rights reserved.
//
//  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
//  following conditions are met:
//
//    o  Redistributions of source code must retain the above copyright notice, this list of conditions and the
//       following disclaimer.
//    o  Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
//       following disclaimer in the documentation and/or other materials provided with the distribution.
//    o  Neither the name of the Miggy Project nor the names of its contributors may be used to endorse or promote
//       products derived from this software without specific prior written permission.
//
//  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
//  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
//  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
//  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
//  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
//  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
//  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// $Revision: 21 $
*/

public class MC68000 implements MC680x0
{
	protected int[] data_regs;
	protected int[] addr_regs;
	protected int reg_pc;
	protected short reg_sr;
	protected int reg_usp;
	protected int reg_ssp;

	/** The address of the current instruction being decoded/disassembled
	 *  This is stored before the fetching the opcode or any extended word(s)
	 *  attached to the opcode.  This means it points to the start of the
	 *  current instruction.
	 */
	protected int currentInstructionAddress;

	protected InstructionSet iset;

	public MC68000()
	{
		data_regs = new int[8];
		addr_regs = new int[8];
		reg_pc = 0;
		reg_sr = 0;
		reg_usp = 0;
		reg_ssp = 0;
	}

	public void init(MachineDefinition config) throws ConfigurationException
	{
		//load the 68000 instruction set
		File f = new File("68000.ops");
		iset = InstructionSetFactory.load(f);
	}

	public int execute()
	{
		//store the position of the PC so we can reference the complete instruction
		//during processing or problem handling if necessary
		currentInstructionAddress = reg_pc;

		//now fetch the opcode (increments reg_pc)
		int opcode = fetch(Size.Word);

		//execute the opcode
		return iset.get(opcode).execute(opcode);
	}

	public Instruction getInstruction(int opcode)
	{
		return iset.get(opcode);
	}

	public int getDataRegister(int n)
	{
		return data_regs[n];
	}

	public int getDataRegister(int n, Size size)
	{
		return data_regs[n] & size.mask();
	}

	public void setDataRegister(int n, int value)
	{
		data_regs[n] = value;
	}

	public void setDataRegister(int n, int value, Size size)
	{
		data_regs[n] &= ~(size.mask());
		data_regs[n] += (value & size.mask());
	}

	public int getAddrRegister(int n)
	{
		return addr_regs[n];
	}

	public int getAddrRegister(int n, Size size)
	{
		return addr_regs[n] & size.mask();
	}

	public void setAddrRegister(int n, int value)
	{
		addr_regs[n] = value;
		if(n == 7)
		{
			if(isSupervisorMode())
			{
				reg_ssp = value;
			}
			else
			{
				reg_usp = value;
			}
		}
	}

	public void setAddrRegister(int n, int value, Size size)
	{
		addr_regs[n] &= ~(size.mask());
		addr_regs[n] |= (value & size.mask());

		if(n == 7)
		{
			if(isSupervisorMode())
			{
				reg_ssp = addr_regs[n];
			}
			else
			{
				reg_usp = addr_regs[n];
			}
		}
	}

	public int getPC()
	{
		return reg_pc;
	}

	public void setPC(int address)
	{
		//24-bit addressing
		reg_pc = address & 0x00ffffff;
	}

	public short getSR()
	{
		return reg_sr;
	}

	public void setSR(short value)
	{
		//todo: sort this out
		//check for supervisor bit change
		if(((reg_sr & SUPER_FLAG) ^ (value & SUPER_FLAG)) != 0)
		{
			//if changing via this method don't push/pop sr and pc - this is only called by andi/eori/ori
			//change of supervisor state
			//setSupervisorMode((value & SUPER_FLAG) != 0);

			if((value & SUPER_FLAG) != 0)
			{
				reg_usp = addr_regs[7];
				addr_regs[7] = reg_ssp;
			}
			else
			{
				//switch stacks
				reg_ssp = addr_regs[7];
				addr_regs[7] = reg_usp;
			}
		}

		reg_sr = value;
	}

	public byte getCCR()
	{
		return (byte)(reg_sr & 0x00ff);
	}

	public void setCCR(byte value)
	{
		reg_sr = (short)((reg_sr & 0xff00) + value);
	}

	public int fetch(Size size)
	{
		//test: changed to peek
		int i;

		if(size == Size.Byte)
		{
			i = SystemModel.MEM.peek(reg_pc, Size.Word);
			i &= 0x00ff;
		}
		else
		{
			i = SystemModel.MEM.peek(reg_pc, size);
		}

		reg_pc += size.readSize();
		return i;
	}

	public void setFlag(CpuFlag flag)
	{
		reg_sr |= flag.bit();
	}

	public void clrFlag(CpuFlag flag)
	{
		reg_sr &= flag.mask();
	}

	public boolean isSet(CpuFlag flag)
	{
		return (reg_sr & flag.bit()) != 0;
	}

	public boolean isClr(CpuFlag flag)
	{
		return (reg_sr & flag.bit()) == 0;
	}

	public void push(int value, Size size)
	{
		addr_regs[7] -= size.byteSize();
		if(isSupervisorMode())
			reg_ssp = addr_regs[7];
		else
			reg_usp = addr_regs[7];

		SystemModel.MEM.poke(addr_regs[7], value, size);
	}

	public int pop(Size size)
	{
		int val = SystemModel.MEM.peek(addr_regs[7], size);
		addr_regs[7] += size.byteSize();

		if(isSupervisorMode())
			reg_ssp = addr_regs[7];
		else
			reg_usp = addr_regs[7];

		return val;
	}

	public int getUSP()
	{
		return reg_usp;
	}

	public void setUSP(int address)
	{
		reg_usp = address;
		if(!isSupervisorMode())
			addr_regs[7] = reg_usp;
	}

	public int getSSP()
	{
		return reg_ssp;
	}

	public void setSSP(int address)
	{
		reg_ssp = address;

		if(isSupervisorMode())
			addr_regs[7] = reg_ssp;
	}

	public void setSupervisorMode(boolean enable)
	{
		if(enable)
		{
			//switch to supervisor mode
			if((reg_sr & SUPER_FLAG) == 0)
			{
				short old_sr = reg_sr;

				reg_sr |= SUPER_FLAG;	//set supervisor bit
				//switch stacks
				reg_usp = addr_regs[7];
				addr_regs[7] = reg_ssp;

				//save pc and status regs
				push(reg_pc, Size.Long);
				push(old_sr, Size.Word);
			}
		}
		else
		{
			//switch back to user mode
			if((reg_sr & SUPER_FLAG) != 0)
			{
//				reg_sr &= SUPER_FLAG_MASK;	//clear supervisor bit

				//restore PC and status regs
				reg_sr = (short)pop(Size.Word);
				reg_pc = pop(Size.Long);

				//switch stacks
				reg_ssp = addr_regs[7];
				addr_regs[7] = reg_usp;
			}
		}
	}

	public boolean isSupervisorMode()
	{
		return ((reg_sr & SUPER_FLAG) != 0);
	}

	public int getCurrentInstructionAddress()
	{
		return currentInstructionAddress;
	}

	public void raiseSRException()
	{
		//always a privilege violation - vector 8
		int address = 32;

		//switch to supervisor mode
		short old_sr = reg_sr;

		if((reg_sr & SUPER_FLAG) == 0)
		{
			reg_sr |= SUPER_FLAG;	//set supervisor bit
			//switch stacks
			reg_usp = addr_regs[7];
			addr_regs[7] = reg_ssp;
		}

		//save pc and status regs - operands fetched in supervisor mode so PC at current address
		push(currentInstructionAddress, Size.Long);
		push(old_sr, Size.Word);

		//todo: handle special exception cases & build stack info

		int xaddress = SystemModel.MEM.peek(address, Size.Long);
		if(xaddress == 0)
		{
			//interrupt vector is uninitialised
			//raise a uninitialised interrupt vector exception instead
			//vector 15 == 0x003c
			xaddress = SystemModel.MEM.peek(0x003c, Size.Long);
			//if this is zero as well the CPU should halt
			if(xaddress == 0)
			{
				throw new CpuException("Interrupt vector not set for unintialised interrupt vector while trapping uninitialised vector 8");
			}
		}

		reg_pc = xaddress;
	}


	public void raiseException(int vector)
	{
		int address = (vector & 0x00ff) << 2;

//		setSupervisorMode(true);
		//switch to supervisor mode
		short old_sr = reg_sr;

		if((reg_sr & SUPER_FLAG) == 0)
		{
			reg_sr |= SUPER_FLAG;	//set supervisor bit
			//switch stacks
			reg_usp = addr_regs[7];
			addr_regs[7] = reg_ssp;
		}

		//save pc and status regs
		push(reg_pc, Size.Long);
		push(old_sr, Size.Word);

		//todo: handle special exception cases & build stack info 

		int xaddress = SystemModel.MEM.peek(address, Size.Long);
		if(xaddress == 0)
		{
			//interrupt vector is uninitialised
			//raise a uninitialised interrupt vector exception instead
			//vector 15 == 0x003c
			xaddress = SystemModel.MEM.peek(0x003c, Size.Long);
			//if this is zero as well the CPU should halt
			if(xaddress == 0)
			{
				throw new CpuException("Interrupt vector not set for unintialised interrupt vector while trapping uninitialised vector " + vector);
			}
		}

		reg_pc = xaddress;
	}

	public void raiseInterrupt(int priority)
	{
		if(priority == 0)
			return;

		int vector = priority + 24;

		raiseException(vector);

		reg_sr &= MC680x0.INT_FLAGS_MASK;
		reg_sr |= (vector << 8);
	}

	public void reset()
	{
		//memory should be reset before the CPU
//		reg_ssp = SystemModel.MEM.peek(0, Size.Long);
		reg_ssp = SystemModel.MEM.peek(0, Size.Long);
		addr_regs[7] = reg_ssp;
//		reg_pc = SystemModel.MEM.peek(4, Size.Long);
		reg_pc = SystemModel.MEM.peek(0x4, Size.Long);
		//supervisor mode, interrupts enabled
		reg_sr = 0x2700;
	}
}
